﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Workflow;
using System.Xml.Linq;
using System.Text;
using Microsoft.SharePoint.Navigation;

namespace $safeprojectname$
{
    /// <summary>
    /// Provides several misc. SharePoint utilities
    /// </summary>
    public static class SPCustomUtilities
    {
        /// <summary>
        /// Establishes an HTTPContext for the specified SharePoint web. This is necessary when performing
        /// some functions from the command line (e.g. Features activated via PowerShell).
        /// </summary>
        /// <param name="web">A reference to the SharePoint web that will be used for the HTTPContext</param>
        public static void EstablishFakeHttpContextForSharePoint(SPWeb web)
        {
            var request = new HttpRequest("", web.Url, "");
            var sw = new System.IO.StringWriter();
            var hr = new HttpResponse(sw);
            HttpContext.Current = new HttpContext(request, hr);
            Microsoft.SharePoint.WebControls.SPControl.SetContextWeb(HttpContext.Current, web);
        }

        /// <summary>
        /// Restricts the list of available page layouts for a web based on a list of specified page layouts and sets the default page layout
        /// </summary>
        /// <param name="web">A reference to the web that contains the Web Part Gallery.</param>
        /// <param name="pageLayoutNames">A list of the page layout file names to be (e.g. ArticleLeft.aspx)</param>
        /// <param name="defaultPageLayoutName">The file name of the page layout to be used (e.g. ArticleLeft.aspx).</param>
        public static void SetPageLayoutsForWeb(SPWeb web, List<string> pageLayoutNames, string defaultPageLayoutName)
        {
            const string pageLayoutPath = "_catalogs/masterpage/";

            try
            {
                // Get a reference to the pubslishing site object
                var pWeb = PublishingWeb.GetPublishingWeb(web);
                var pSite = new PublishingSite(web.Site);

                // Get the page layout collection for the site
                var allPageLayouts = pSite.GetPageLayouts(true);
                PageLayout defaultPageLayout = null;
                var pageLayoutsForSite = new List<PageLayout>();

                foreach (var pageLayoutName in pageLayoutNames)
                {
                    var pageLayout = allPageLayouts[pageLayoutPath + pageLayoutName];
                    pageLayoutsForSite.Add(pageLayout);

                    if (pageLayoutName == defaultPageLayoutName)
                    {
                        defaultPageLayout = pageLayout;
                    }
                }

                // Set the available page layouts 
                pWeb.SetAvailablePageLayouts(pageLayoutsForSite.ToArray(), false);

                if (defaultPageLayout != null)
                {
                    // Set the default page layout
                    pWeb.SetDefaultPageLayout(defaultPageLayout, false);
                }

                // Update the web and commit the changes
                pWeb.Update();
            }
            catch
            {
                // ToDo
                throw;
            }
        }

        /// <summary>
        /// Sets the list of available content types for the specified list.
        /// </summary>
        /// <param name="contentTypeIds">A list of SPContentTypeId's that should be available on the list.</param>
        /// <param name="removeUnlisted">If true, removes all existing list content types not specified on the contentTypeIds parameter. 
        /// If false, only adds new content types as needed. Default is true.</param>
        public static void SetContentTypesForList(SPWeb web, SPList list, List<SPContentTypeId> contentTypeIds, bool reorder, bool removeUnlisted = true)
        {
            // Ensure "Allow management of content types" is turned on.
            if (false == list.ContentTypesEnabled)
            {
                list.ContentTypesEnabled = true;
                list.Update();
            }

            foreach (SPContentTypeId contentTypeId in contentTypeIds)
            {
                SPContentTypeId foundCt = default(SPContentTypeId);
                try
                {
                    foundCt = list.GetDirectChildContentType(contentTypeId);
                }
                catch (Exception) { }

                if (foundCt != default(SPContentTypeId))
                {
                    // List already contains this content type.
                    continue;
                }

                var contentType = web.AvailableContentTypes[contentTypeId];
                if (contentType == null)
                {
                    string errorMessage = string.Format("Could not find content type {0} to add it to the list: {1}", contentTypeId, list.RootFolder.ServerRelativeUrl);
                    SPCustomUtilities.LogMessage(errorMessage, EventSeverity.Error);
                    throw new SPException(errorMessage);
                }
                list.ContentTypes.Add(contentType);
            }

            if (removeUnlisted)
            {
                // Get a list of content types to remove.
                List<SPContentType> contentTypesToRemove = new List<SPContentType>();
                foreach (SPContentType listCt in list.ContentTypes)
                {
                    bool onTheList = false;
                    foreach (SPContentTypeId allowedCt in contentTypeIds)
                        if (listCt.Id.IsChildOf(allowedCt))
                            onTheList = true;

                    if (false == onTheList)
                    {
                        contentTypesToRemove.Add(listCt);
                    }
                }

                // Remove the content types not on the list.
                foreach (SPContentType ctToRemove in contentTypesToRemove)
                {
                    try
                    {
                        if (ctToRemove.ReadOnly)
                        {
                            ctToRemove.ReadOnly = false;
                            ctToRemove.Update();
                        }

                        list.ContentTypes.Delete(ctToRemove.Id);
                    }
                    catch (Exception ex)
                    {
                        string errorMessage = string.Format("Couldn't delete content type '{0}' on the list: {1}", ctToRemove.Name, list.RootFolder.ServerRelativeUrl);
                        SPCustomUtilities.LogException(errorMessage, ex);
                    }
                }
            }
            list.Update();

            if (reorder)
            {
                IList<SPContentType> newCtOrder = new List<SPContentType>();
                for (int i = 0; i < list.ContentTypes.Count; i++)
                {
                    if (i >= contentTypeIds.Count)
                        continue;
                    SPContentTypeId contentTypeId = list.ContentTypes.BestMatch(contentTypeIds[i]);
                    SPContentType listCt = list.ContentTypes[contentTypeId];
                    if (listCt.Id.Parent != SPBuiltInContentTypeId.Folder & listCt.Id.Parent != SPBuiltInContentTypeId.UntypedDocument)
                    {
                        newCtOrder.Add(listCt);
                    }
                }
                SPFolder rootFolder = list.RootFolder;
                rootFolder.UniqueContentTypeOrder = newCtOrder;
                rootFolder.Update();
            }
        }

        public static string GetVariationRootUrl(SPWeb web, bool returnFullUrl)
        {
            try
            {
                var rootWeb = web.Site.RootWeb;

                var variationsRelationshipList =
                    rootWeb.Lists[new Guid(rootWeb.AllProperties["_VarRelationshipsListId"].ToString())];

                if (variationsRelationshipList.ItemCount > 0)
                {
                    var item = variationsRelationshipList.Items[0];
                    object itemValue = item["ObjectID"];
                    if (itemValue == null)
                        return string.Empty;

                    string itemValueString = itemValue.ToString();

                    // The item is stored as a comma seperated value (e.g. http://home.yoursite.com/variationroot, /variationroot)
                    // So we will load them into a string array for easy retrieval
                    var variationUrls = itemValueString.Split(',');

                    if (returnFullUrl == true)
                    {
                        return variationUrls[0].Trim();
                    }
                    else
                    {
                        return variationUrls[1].Trim();
                    }
                }
                else
                {
                    return string.Empty;
                }
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        public static bool CheckIfWebExists(string url)
        {
            using (SPSite site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb(url))
                {
                    if (web.Exists)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
        }

        public static bool IsSiteUsingVariations(SPWeb web)
        {
            // Format the web app relative URL of the site collection so that it always ends with a "/"
            // This helps with site collections that are below the root (e.g. "/sites/searchcenter")
            string relativeUrlOfSiteCollection = web.Site.RootWeb.ServerRelativeUrl;
            if (!relativeUrlOfSiteCollection.EndsWith("/"))
            {
                relativeUrlOfSiteCollection += "/";
            }
            // If variations is enabled at the root level for this site
            if (SPCustomUtilities.GetVariationRootUrl(web, false) == relativeUrlOfSiteCollection)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public static void LogException(string userErrorMessage, Exception ex)
        {
            userErrorMessage += Environment.NewLine + "Exception Message: " + ex.Message;
#if DEBUG
            userErrorMessage += Environment.NewLine + ex.StackTrace.ToString();
#endif
            SPCustomUtilities.LogMessage(userErrorMessage, Microsoft.SharePoint.Administration.EventSeverity.Error);
        }

        public static void LogMessage(string message, EventSeverity severity)
        {
            SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
            if (diagSvc != null)
            {
                // Write to the Event log.
                try
                {
                    // This will fail if the account running the logging service doesn't have event tracing permissions on the server.
                    diagSvc.WriteEvent(0, new SPDiagnosticsCategory("$clientName$ $projectName$", TraceSeverity.Monitorable, severity), severity, message, new object[0]);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot write to the event log. Error message: " + ex.Message);
                }
                // Write to the ULS log.
                try
                {
                    diagSvc.WriteTrace(
                                0,
                                new SPDiagnosticsCategory("$clientName$ $projectName$", TraceSeverity.Monitorable, severity),
                                TraceSeverity.Monitorable,
                                message,
                                new object[0]);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot write to the event log. Error message: " + ex.Message);
                }
            }
            else
            {
                Console.WriteLine("Messages can only be logged when running on a SharePoint server.");
            }
        }

        public static string RemoveReservedCharacters(string stringToClean)
        {
            if (string.IsNullOrWhiteSpace(stringToClean))
            {
                return stringToClean;
            }

            var reservedChars = "|\\\"'/[]:<>+=,;?*@".ToCharArray();

            StringBuilder cleanStringBuilder = new StringBuilder();
            foreach (char stringChar in stringToClean)
            {
                if (false == reservedChars.Contains(stringChar))
                {
                    cleanStringBuilder.Append(stringChar);
                }
            }

            return cleanStringBuilder.ToString();
        }
    }

    public static class TaxonomyHelpers
    {
        public static SPField GetSiteColumn(SPWeb web, Guid fieldId)
        {
            SPField field = null;

            try
            {
                field = web.Fields[fieldId];
            }
            catch { }

            return field;
        }

        public static SPField GetSiteColumn(SPWeb web, string fieldName)
        {
            SPField field = null;

            try
            {
                field = web.Fields[fieldName];
            }
            catch { }

            return field;
        }

        public static TermStore GetTermStore(TaxonomySession session, string termStoreName)
        {
            TermStore termStore = null;

            try
            {
                termStore = session.TermStores[termStoreName];
            }
            catch { }

            return termStore;
        }

        public static TermStore GetTermStore(TaxonomySession session, bool useDefaultTermStore)
        {
            TermStore termStore = null;

            try
            {
                termStore = session.DefaultSiteCollectionTermStore;
            }
            catch { }

            return termStore;
        }

        public static Group GetTermGroup(TermStore termStore, string termGroupName)
        {
            Group group = null;

            try
            {
                group = termStore.Groups[termGroupName];
            }
            catch { }

            return group;
        }

        public static TermSet GetTermSet(Group group, string termSetName)
        {
            TermSet termSet = null;

            try
            {
                termSet = group.TermSets[termSetName];
            }
            catch { }

            return termSet;
        }

        public static TermSet GetTermSet(TermStore termStore, Guid termSetId)
        {
            TermSet termSet = null;

            try
            {
                termSet = termStore.GetTermSet(termSetId);
            }
            catch { }

            return termSet;
        }

        public static string GetTermValueString(Term metadataTerm, System.Globalization.CultureInfo locale)
        {
            // Create a default term value string in the format: WssId;#TermDefaultLabel|TermGuid
            // We don't know the WssId so default to -1 (and SharePoint will set it for us).
            string termDefaultValueString = string.Concat(
                "-1;#", 
                metadataTerm.GetDefaultLabel(locale.LCID), 
                TaxonomyField.TaxonomyGuidLabelDelimiter, metadataTerm.Id
                );

            return termDefaultValueString;
        }


        /// <summary>
        /// Gets all terms, and optionally labels, in a given term path of a given term set.
        /// </summary>
        /// <param name="termPath">An array of path segments representing the hierarchy path to search for.</param>
        /// <param name="locale">The locale of the Term.Labels to be matched.</param>
        /// <param name="defaultLabelOnly">Indicates whether only default labels should be included in the search.</param>
        /// <param name="trimUnavailable">A Boolean indicating whether to trim out Term objects that have the IsAvailableForTagging property set to false.</param>
        /// <returns></returns>
        public static Term GetTermByNameAndPath(this TermSet termSetToSearch, string[] termPath, System.Globalization.CultureInfo locale, bool defaultLabelOnly, bool trimUnavailable)
        {
            if (termSetToSearch == null | termPath == null | locale == null)
                return null;
            if (termPath.Length < 1)
                return null;

            Term termToReturn = null;

            // TermSet.GetTerms using a string searches the whole tree of terms in the entire TermSet.
            var siteNameTermResults = termSetToSearch.GetTerms(termPath.Last(), locale.LCID, defaultLabelOnly, StringMatchOption.ExactMatch, 100, trimUnavailable);
            if (siteNameTermResults.Count > 0)
            {
                // Check the GetTerms() results to find a Term with the same path.
                string termPathJoined = string.Join(";", termPath);

                foreach (Term termWithSiteName in siteNameTermResults)
                {
                    // Get the term path. SharePoint replaces the regular ampersand with a full width unicode ampersand. Swap it back for comparison.
                    string metadataTermPath = termWithSiteName.GetPath(locale.LCID).Replace((char)65286, (char)38);

                    if (string.Equals(metadataTermPath, termPathJoined, StringComparison.InvariantCultureIgnoreCase))
                    {
                        // The term path is correct. Return the term for this site.
                        termToReturn = termWithSiteName;
                        break;
                    }
                }
            }

            return termToReturn;
        }
    }

    #region Extension Classes

    public static class SPSiteExtension
    {
        /// <summary>
        /// Returns the web app relative URL of the site collection so that it always ends with a "/"
        /// This helps when doing comparisons or string building with site collections that are
        /// below the root (e.g. "/sites/searchcenter")
        /// </summary>
        public static string GetServerRelativeUrlWithGuaranteedSlash(this SPSite site)
        {
            // Format the web app relative URL of the site collection so that it always ends with a "/"
            // This helps with site collections that are below the root (e.g. "/sites/searchcenter")
            string relativeUrlOfSiteCollection = site.RootWeb.ServerRelativeUrl;
            if (!relativeUrlOfSiteCollection.EndsWith("/"))
            {
                relativeUrlOfSiteCollection += "/";
            }
            return relativeUrlOfSiteCollection;
        }

        /// <summary>
        /// This function attempts to retrieve the requested feature ID from the site's FeatureDefinitions collection
        /// using the feature name. If the feature is not found in the collection an empty guid is returned (e.g. Guid.Empty)
        /// </summary>
        /// <param name="site">The site containing the feature definition</param>
        /// <param name="featureName">The name of the feature</param>
        /// <returns></returns>
        public static Guid TryGetFeatureIdByName(this SPSite site, string featureName)
        {
            var featureId = Guid.Empty;
            var featureDefinition = site.FeatureDefinitions[featureName];

            if (featureDefinition != null)
            {
                featureId = featureDefinition.Id;
            }

            return featureId;
        }

        public static XElement GetWebPartXmlFromCatalog(this SPSite site, string webPartName)
        {
            XElement webPartXml = default(XElement);

            const string webPartGalleryPath = "/_catalogs/wp/";
            SPFile webPartFile = site.RootWeb.GetFile(webPartGalleryPath + webPartName);
            if (webPartFile.Exists)
            {
                
                try
                {
                    using (System.IO.Stream stream = webPartFile.OpenBinaryStream())
                    {
                        webPartXml = System.Xml.Linq.XElement.Load(stream);
                    }
                }
                catch (Exception ex)
                {
                    SPCustomUtilities.LogException(string.Format("Error parsing web part XML from the file: {0}", webPartFile.ServerRelativeUrl), ex);
                }
            }
            else
            {
                SPCustomUtilities.LogMessage(string.Format("The web part definition file {0} does not exist.", webPartFile), EventSeverity.Error);
            }

            return webPartXml;
        }
    }

    public static class SPWebExtension
    {
        public static SPList TryGetListByName(this SPWeb web, string listName)
        {
            SPList list = null;
            try
            {
                list = web.Lists[listName];
            }
            catch (Exception) { }

            return list;
        }

        public static SPGroup TryGetGroup(this SPWeb web, string groupName)
        {
            SPGroup group = null;
            try
            {
                group = web.SiteGroups[groupName];
            }
            catch (Exception) { }

            return group;
        }

        public static SPRoleDefinition TryGetRoleDefinition(this SPWeb web, string roleName)
        {
            SPRoleDefinition role = null;
            try
            {
                role = web.RoleDefinitions[roleName];
            }
            catch (Exception) { }

            return role;
        }

        public static SPWorkflowTemplate TryGetWorkflowTemplate(this SPWeb web, Guid templateId)
        {
            SPWorkflowTemplate template = null;
            try
            {
                template = web.WorkflowTemplates[templateId];
            }
            catch (Exception) { }

            return template;
        }

        public static SPWorkflowTemplate TryGetWorkflowTemplateByName(this SPWeb web, string templateName)
        {
            SPWorkflowTemplate template = web.WorkflowTemplates
                .Cast<SPWorkflowTemplate>()
                .Where(wt => string.Equals(wt.Name, templateName, StringComparison.CurrentCultureIgnoreCase))
                .FirstOrDefault();

            return template;
        }

        public static string GetCleanTitle(this SPWeb web)
        {
            return SPCustomUtilities.RemoveReservedCharacters(web.Title);
        }
    }

    public static class SPNavigationExtensions
    {
        public static SPNavigationNode FindNodeByName(this SPNavigationNodeCollection collectionToSearch, string name, bool recursive)
        {
            SPNavigationNode foundNode = null;
            foreach (SPNavigationNode node in collectionToSearch)
            {
                if (string.Equals(node.Title, name, StringComparison.CurrentCultureIgnoreCase))
                {
                    foundNode = node;
                    break;
                }
                else if (recursive & node.Children.Count > 0)
                {
                    foundNode = node.Children.FindNodeByName(name, recursive);
                    if (foundNode != null)
                    {
                        break;
                    }
                }
            }

            return foundNode;
        }

        public static void SetAudience(this SPNavigationNode node, string[] globalGroups, string[] distributionLists, string[] sharePointGroups)
        {
            string globalGroupsJoined = "";
            string distributionListsJoined = "";
            string sharepointGroupsJoined = "";
            // Join the groups into comma-separated strings.
            if (globalGroups != null && globalGroups.Length > 0)
                globalGroupsJoined = string.Join(",", globalGroups);
            if (distributionLists != null)
                distributionListsJoined = string.Join("\n", distributionLists);
            if (sharePointGroups != null)
                sharepointGroupsJoined = string.Join(",", sharePointGroups);

            // Format is: [Global Group 1],[Global Group 2];;[Distribution List 1],[Distribution List 2];;[SharePoint Group 1],[SharePoint Group 2]
            string audTargProp = string.Format("{0};;{1};;{2}", globalGroupsJoined, distributionListsJoined, sharepointGroupsJoined);
            if (false == node.Properties.Contains("Audience"))
            {
                node.Properties.Add("Audience", audTargProp);
            }
            else
            {
                node.Properties["Audience"] = audTargProp;
            }
            node.Update();
        }
    }

    public static class SPListExtension
    {
        public static void SetAudienceTargeting(this SPList list, bool enabled)
        {
            SPField audienceField = list.ParentWeb.Site.RootWeb.Fields[FieldId.AudienceTargeting];
            if (enabled & false == list.Fields.Contains(audienceField.Id))
            {
                XElement fieldElement = new XElement("Field",
                        new XAttribute("ID", audienceField.Id),
                        new XAttribute("Type", audienceField.TypeAsString),
                        new XAttribute("Name", audienceField.InternalName),
                        new XAttribute("DisplayName", audienceField.Title),
                        new XAttribute("Required", "FALSE")
                        );

                list.Fields.AddFieldAsXml(fieldElement.ToString(), false, SPAddFieldOptions.AddFieldInternalNameHint);
                list.Update();
                SPCustomUtilities.LogMessage("Enabled audience targeting on list: " + list.RootFolder.ServerRelativeUrl, EventSeverity.Information);
            }
            else if (!enabled & list.Fields.Contains(audienceField.Id))
            {
                // Make sure the field can be deleted.
                var listAudienceField = list.Fields[audienceField.Id];
                listAudienceField.AllowDeletion = true;
                listAudienceField.Sealed = false;
                listAudienceField.Update();

                list.Fields.Delete(listAudienceField.InternalName);
                list.Update();
                SPCustomUtilities.LogMessage("Disabled audience targeting on list: " + list.RootFolder.ServerRelativeUrl, EventSeverity.Information);
            }
        }

        public static SPView TryGetViewByDisplayName(this SPList list, string displayName)
        {
            SPView view = null;

            try
            {
                view = list.Views[displayName];
            }
            catch { }

            return view;
        }
    }

    public static class SPListItemExtension
    {
        public static void SetAudience(this SPListItem item, string[] globalGroups, string[] distributionLists, string[] sharePointGroups)
        {
            string globalGroupsJoined = "";
            string distributionListsJoined = "";
            string sharepointGroupsJoined = "";
            // Join the groups into comma-separated strings.
            if (globalGroups != null && globalGroups.Length > 0)
                globalGroupsJoined = string.Join(",", globalGroups);
            if (distributionLists != null)
                distributionListsJoined = string.Join("\n", distributionLists);
            if (sharePointGroups != null)
                sharepointGroupsJoined = string.Join(",", sharePointGroups);

            // Format is: [Global Group 1],[Global Group 2];;[Distribution List 1],[Distribution List 2];;[SharePoint Group 1],[SharePoint Group 2]
            string audTargProp = string.Format("{0};;{1};;{2}", globalGroupsJoined, distributionListsJoined, sharepointGroupsJoined);
            if (false == item.Properties.Contains("Audience"))
            {
                item.Properties.Add("Audience", audTargProp);
            }
            else
            {
                item.Properties["Audience"] = audTargProp;
            }
            item.Update();
        }
    }

    #endregion
}
